
#include <BOpenGL>
#include "ILineBlock.h"
#include "member_BTexture.h"

using namespace BWE;

ILineBlock::ILineBlock()
{
	visible = true;
	dirtyFont = true;
	dirtyCells = true;
	rowHeight = 0;
	align = Align_Left;
	inputIndex = 0;
	inputCount = 0;
}
ILineBlock::ILineBlock(const ILineBlock& other)
{
	visible = true;
	dirtyCells = true;
	inputIndex = 0;
	inputCount = 0;
	rowHeight = other.rowHeight;
	range = other.range;
	rect = other.rect;
	cells = other.cells;
	align = other.align;
	_font = other._font;
	dirtyFont = other.dirtyFont;
}
ILineBlock::~ILineBlock()
{

}

void ILineBlock::reset()
{
	if (cells.size() > text.size() * 4)
		cells.clear();
	else
		cells.reset();
	const char* cstr = text.cstr();
	for (auto it = text.begin(); it < text.end(); it++)
	{
		BCode code = it.code();
		cells.append(CodeCell());
		CodeCell& cell = cells.last();
		cell.code = code;
		cell.fresh(_font, rowHeight);
	}
	inputIndex = 0;
	inputCount = 0;
	dirtyCells = true;
}
void ILineBlock::reset(const BString& text)
{
	this->text = text;
	reset();
}
void ILineBlock::append(const BString& text)
{
	this->text << text;
	for (auto it = text.begin(); it < text.end(); it++)
	{
		cells.append(CodeCell());
		CodeCell& cell = cells.last();
		cell.code = it.code();
		cell.fresh(_font, rowHeight);
	}
	dirtyCells = true;
}

bool ILineBlock::input(const BCode& code)
{
	if (!cells.insert(inputIndex, CodeCell()))
		return false;
	CodeCell& cell = cells[inputIndex];
	cell.code = code;
	cell.fresh(_font, rowHeight);
	inputIndex++;
	dirtyCells = true;
	flushText();
	return true;
}
bool ILineBlock::input(const BString& text)
{
	int count = text.count();
	if (!cells.insert(inputIndex, CodeCell(), count))
		return false;
	for (auto it = text.begin(); it.valid(); it++)
	{
		CodeCell& cell = cells[inputIndex++];
		cell.code = *it;
		cell.fresh(_font, rowHeight);
	}
	dirtyCells = true;
	flushText();
	return true;
}
bool ILineBlock::remove(int pos, int count)
{
	if (!cells.remove(pos, count))
		return false;
	dirtyCells = true;
	flushText();
	return true;
}
BString ILineBlock::substr(int pos, int count) const
{
	BString str;
	if (pos < 0 || pos >= cells.size())
		return str;
	if (count < 1)
		return str;
	int stop = pos + count;
	if (stop > cells.size())
		return str;
	for (int i = pos; i < stop; i++)
	{
		str.append(cells[i].code);
	}
	return str;
}
BString ILineBlock::selection() const
{
	BString str;
	if (inputIndex < 0 || inputIndex >= cells.size())
		return str;
	if (inputCount == 0)
		return str;
	int begin = bMin(inputIndex, inputIndex + inputCount);
	int end = bMax(inputIndex, inputIndex + inputCount);
	for (int i = begin; i < end; i++)
	{
		str.append(cells[i].code);
	}
	return str;
}

bool ILineBlock::selectWord()
{
	if (cells.empty())
		return false;

	if (!cells.check(inputIndex))
		return false;

	bool punct = cells[inputIndex].code.isPunct();

	int begin = inputIndex;
	while (begin >= 0)
	{
		BCode& code = cells[begin].code;
		if (code < 255)
		{
			if (cells[begin].code.isSpace())
				break;
			if (punct != cells[begin].code.isPunct())
				break;
		}
		begin--;
	}
	begin++;
	int end = begin;
	while (end < cells.size())
	{
		BCode& code = cells[end].code;
		if (code < 255)
		{
			if (cells[end].code.isSpace())
				break;
			if (punct != cells[end].code.isPunct())
				break;
		}
		end++;
	}
	inputIndex = begin;
	inputCount = end - begin;
	return inputCount > 0;
}
bool ILineBlock::removeSelection()
{
	int pos = bMin(inputIndex, inputIndex + inputCount);
	if (cells.check(pos) && inputCount)
	{
		cells.remove(pos, abs(inputCount));
		inputIndex = pos;
		inputCount = 0;
		dirtyCells = true;
		flushText();
		return true;
	}
	return false;
}

bool ILineBlock::seekHead()
{
	if (inputIndex != 0 || inputCount != 0)
	{
		inputIndex = 0;
		inputCount = 0;
		return true;
	}
	return false;
}
bool ILineBlock::seekPoint(const BPoint& point)
{
	int index = indexAt(point);
	if (inputIndex != index || inputCount != 0)
	{
		inputIndex = index;
		inputCount = 0;
		return true;
	}
	return false;
}
bool ILineBlock::seekTail()
{
	int tail = cells.size();
	if (cells.last().code == '\n')
		tail = cells.size() - 1;
	if (inputIndex != tail || inputCount != 0)
	{
		inputIndex = tail;
		inputCount = 0;
		return true;
	}
	return false;
}

int ILineBlock::seekPrevCode()
{
	inputIndex--;
	inputCount = 0;
	return inputIndex;
}
int ILineBlock::seekNextCode()
{
	inputIndex++;
	inputCount = 0;
	return inputIndex;
}
int ILineBlock::seekPrevWord()
{
	inputCount = 0;
	if (cells(inputIndex - 1).code.isPunct())
	{
		inputIndex--;
		return inputIndex;
	}
	if (cells(inputIndex - 1).code.isSpace())
	{
		while (inputIndex > 0)
		{
			if (!cells[inputIndex - 1].code.isSpace())
				break;
			inputIndex--;
		}
		return inputIndex;
	}
	if (cells(inputIndex - 1).code.isDigit())
	{
		while (inputIndex > 0)
		{
			if (!cells[inputIndex - 1].code.isDigit())
				break;
			inputIndex--;
		}
		return inputIndex;
	}
	while (inputIndex > 0)
	{
		if (cells[inputIndex - 1].code.isSpace())
			break;
		if (cells[inputIndex - 1].code.isDigit())
			break;
		if (cells[inputIndex - 1].code.isPunct())
			break;
		inputIndex--;
	}
	return inputIndex;
}
int ILineBlock::seekNextWord()
{
	int size = cells.size();
	if (inputIndex == size)
	{
		inputIndex++;
		return inputIndex;
	}
	if (cells[inputIndex].code.isPunct())
	{
		inputIndex++;
		return inputIndex;
	}
	if (cells[inputIndex].code.isSpace())
	{
		while (inputIndex < size)
		{
			if (!cells[inputIndex].code.isSpace())
				break;
			inputIndex++;
		}
		return inputIndex;
	}
	if (cells[inputIndex].code.isDigit())
	{
		while (inputIndex < size)
		{
			const BCode& code = cells[inputIndex].code;
			if (code < 255)
			{
				if (!code.isDigit())
					break;
			}
			inputIndex++;
		}
		return inputIndex;
	}
	while (inputIndex < size)
	{
		const BCode& code = cells[inputIndex].code;
		if (code < 255)
		{
			if (code.isSpace())
				break;
			if (code.isPunct())
				break;
			if (code.isDigit())
				break;
		}
		inputIndex++;
	}
	return inputIndex;
}

int ILineBlock::indexAt(const BPoint& pos) const
{
	if (pos.y() < rect.top())
		return -1;
	if (pos.y() > rect.bottom())
		return cells.size();
	BPoint point = pos - rect.pos();
	int index = 0;
	while (index < cells.size())
	{
		BRect rect = cells[index].rect;
		if (rect.bottom() < point.y())
		{
			index++;
			continue;
		}
		if (rect.top() > point.y())
			break;
		if (rect.center().x() > point.x())
			break;
		index++;
	}
	return index;
}
int ILineBlock::wordWidth(int index) const
{
	int width = cells[index].rect.width();
	const BCode& code = cells[index].code;
	const char* ustr = code.str();
	if (code.isSpace())
		return width;
	if (code.isPunct())
		return width;
	if (code.num() > 1)
		return width;
	while (++index < cells.size())
	{
		const BCode& code = cells[index].code;
		if (code.isSpace())
			break;
		if (code.isPunct())
			break;
		if (code.num() > 1)
			break;
		width += cells[index].rect.width();
	}
	return width;
}

void ILineBlock::adjust(int pageWidth)
{
	if (!_font)
		return;
	if (dirtyFont)
	{
		for (int i = 0; i < cells.size(); i++)
		{
			CodeCell& cell = cells[i];
			cell.fresh(_font, rowHeight);
		}
		dirtyFont = false;
		dirtyCells = true;
	}
	if (dirtyCells)
	{
		int realRowHeight = rowHeight;
		if (realRowHeight < 1)
			realRowHeight = _font->size();
		int width = 0;
		int fontWidth = _font->size();
		int fontHeight = _font->size();
		int up = (int)(fontHeight * 0.2);
		int x = 0;
		int y = 0;
		for (int i = 0; i < cells.size(); i++)
		{
			CodeCell& cell = cells[i];
			if (pageWidth > 0)
			{
				int ww = wordWidth(i);
				if (x + ww > pageWidth)
				{
					x = 0;
					y += realRowHeight;
				}
			}
			cell.rect.setPos(x, y);
			cell.image_rect.setPos(x + cell.image_left, cell.rect.bottom() - cell.image_top - up);
			x += cell.rect.width();
			if (width < x)
				width = x;
		}
		textSize.set(width, y + realRowHeight);
		rect.setSize(width, y + realRowHeight);
		if (pageWidth > width)
		{
			int xoffset = 0;
			if (align & Align_Right)
				xoffset = pageWidth - width;
			if (align & Align_HCenter)
				xoffset = (pageWidth - width) / 2;
			if (xoffset > 0)
			{
				for (int i = 0; i < cells.size(); i++)
				{
					CodeCell& cell = cells[i];
					cell.rect.move(xoffset, 0);
					cell.image_rect.move(xoffset, 0);
				}
			}
		}
		dirtyCells = false;
	}
}
void ILineBlock::paint(BPainter& painter, BTextFormat* format)
{
	BColor textColor = painter.color();
	int bottom = painter.clip().bottom();
	BPoint pos = rect.pos();
	painter.unbind();
	glEnable(GL_TEXTURE_2D);
	for (int i = 0; i < cells.size(); i++)
	{
		const CodeCell& cell = cells[i];
		BRect rect = cell.image_rect + pos;
		if (rect.top() > bottom)
			break;
		if (cell.image.empty())
			continue;
		if (format)
		{
			const BFont* font = format->font(range.min() + i);
			if (font)
				painter.setFont(font);
			else
				painter.setFont(_font);
			const BColor* color = format->color(range.min() + i);
			if (color && color->a() > 1)
				painter.setColor(*color);
			else
				painter.setColor(textColor);
		}
		int x = rect.x();
		int y = rect.y();
		int w = rect.width();
		int h = rect.height();
		if (texture_member(cell.image)->dirty)
			cell.image->flush();
		glBindTexture(GL_TEXTURE_2D, texture_member(cell.image)->tbo);
		glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f);		glVertex2i(x, y);
		glTexCoord2f(0.0f, 1.0f);		glVertex2i(x, y + h);
		glTexCoord2f(1.0f, 1.0f);		glVertex2i(x + w, y + h);
		glTexCoord2f(1.0f, 0.0f);		glVertex2i(x + w, y);
		glEnd();
		glBindTexture(GL_TEXTURE_2D, 0);
	}
	glDisable(GL_TEXTURE_2D);
}

